
#include <stdio.h>
#include "../iface/debug.h"
#include "../iface/SSLInterface.h"
#include <openssl/evp.h>
#include "BaseConvert.h"
#include "StringUtils.h"

// Other utils
// SQLSafeprintf
// BurnAndFreeString
// iface utils password and prompt!

char *BaseConvert::GetRandomBytes(int bytes)
{
   int rsize = bytes;
   unsigned char *ranbuf = (unsigned char *)malloc(sizeof(unsigned char)*rsize);
   FILE *FP = fopen("/dev/urandom", "rb");
   if (!FP)
   {
      errprint("Couldn't open /dev/urandom.");
      return NULL;
   }
   int elems = fread(ranbuf, 1, rsize, FP);
   fclose(FP);
   if (elems < rsize)
      errprint("Tried to read %d bytes from /dev/urandom, got %d.", rsize, elems);
   return (char *)ranbuf;
}

char *BaseConvert::ConvertBytesToBase64(const char *data, int bytes)
{
   int databuf_size = apr_base64_encode_len(bytes);
   char *buf64 = (char *)malloc(sizeof(char)*(databuf_size+1));
   int len = apr_base64_encode_binary(buf64, (const unsigned char *)data, bytes);
   if (len > (databuf_size+1))
      errprint("Warning! Base64 encoding produced oversized output (%d > %d).", len, databuf_size+1);
   return buf64;
}

char *BaseConvert::ConvertBytesToHexadecimal(const char *data, int bytes)
{
   int databuf_size = 2*bytes;
   char *bufhex = (char *)malloc(sizeof(char)*(databuf_size+1));
   bufhex[0] = '\0';
   for (int i = 0; i < bytes; i++)
   {
      char buffer[3];
      sprintf(buffer, "%02x", (unsigned char)data[i]);
      memcpy(&(bufhex[i*2]), buffer, sizeof(char)*2);
   }
   bufhex[bytes*2] = '\0';
   int len = strlen(bufhex);
   if (len > (databuf_size+1))
      errprint("Warning! Hexadecimal encoding produced oversized output (%d > %d).", len, databuf_size+1);
   return bufhex;
}

// This one probably requires zero-termination of the string?!!?
char *BaseConvert::ConvertBase64ToBytes(const char *data, int bytes)
{
   char *bufstr = (char *)malloc(sizeof(char)*(bytes+1));
   memcpy(bufstr, data, sizeof(char)*bytes);
   bufstr[bytes] = '\0';
   int databuf_size = apr_base64_decode_len(bufstr);
   char *buf64 = (char *)malloc(sizeof(char)*(databuf_size+1));
   int len = apr_base64_decode_binary((unsigned char *)buf64, bufstr);
   if (len > (databuf_size+1))
      errprint("Warning! Base64 decoding produced oversized output (%d > %d).", len, databuf_size+1);
   free(bufstr);
   return buf64;
}

char *BaseConvert::ConvertBase64ToBytes(const char *string, int *bytes)
{
   char *bufstr = (char *)malloc(sizeof(char)*(strlen(string)+1));
   strcpy(bufstr, string);
   int databuf_size = apr_base64_decode_len(bufstr);
   char *buf64 = (char *)malloc(sizeof(char)*(databuf_size+1));
   int len = apr_base64_decode_binary((unsigned char *)buf64, bufstr);
   if (len > (databuf_size+1))
      errprint("Warning! Base64 decoding produced oversized output (%d > %d).", len, databuf_size+1);
   free(bufstr);
   if (bytes) *bytes = len;
   return buf64;
}

char *BaseConvert::ConvertHexadecimalToBytes(const char *data, int *bytes)
{
   if (bytes == NULL) return NULL;
   int databuf_size = ((*bytes)/2);
   unsigned char *bufhex = (unsigned char *)malloc(sizeof(unsigned char)*(databuf_size));
   for (int i = 0; i < databuf_size; i++)
   {
      char buffer[3];
      memcpy(buffer, &(data[i*2]), sizeof(unsigned char)*2);
      buffer[2] = '\0';
      unsigned int tint;
      sscanf(buffer, "%02x", &tint);
      bufhex[i] = tint;
   }
   (*bytes) = databuf_size;
   return (char *)bufhex;
}

char *BaseConvert::MakeBase64UUID(int bytes)
{
   char *ranbuf = GetRandomBytes(bytes);
   char *buf64 = ConvertBytesToBase64(ranbuf, bytes);
   free(ranbuf);
   return buf64;
}

char *BaseConvert::MakeHexUUID_PlusUniq_deprecated(int bytes, KeyDB *kdb)
{
   while (1)
   {
      char *name = BaseConvert::MakeHexadecimalUUID(bytes);
      if (!(kdb->CheckForPublicKeyByName(name))) return name;
      free(name);
   }
}

char *BaseConvert::MakeHexUUID_PlusUniq2(int bytes, KeyDB *kdb, KeyDB *kdb2)
{
   while (1)
   {
      char *name = BaseConvert::MakeHexadecimalUUID(bytes);
      if ((!(kdb->CheckForPublicKeyByName(name))) &&
          (!(kdb2->CheckForPublicKeyByName(name))))
         return name;
      free(name);
   }
}

char *BaseConvert::MakeHexadecimalUUID(int bytes)
{
   char *ranbuf = GetRandomBytes(bytes);
   char *bufhex = ConvertBytesToHexadecimal(ranbuf, bytes);
   free(ranbuf);
   return bufhex;
}

char *BaseConvert::GetMD5Hash(const char *data, int length)
{
   unsigned char digest[EVP_MAX_MD_SIZE];
   char *md5hash = (char *)malloc(sizeof(char)*(EVP_MAX_MD_SIZE*2+1));
   md5hash[0] = '\0';
   EVP_MD_CTX *ctx;
   ctx = EVP_MD_CTX_create();
   const EVP_MD *md5 = EVP_md5();
   EVP_DigestInit_ex(ctx, md5, NULL);
   EVP_DigestUpdate(ctx, data, length);
   EVP_DigestFinal_ex(ctx, digest, NULL);
   EVP_MD_CTX_destroy(ctx);
   fSSLCheckError();
   for (int i = 0; i < md5->md_size; i++)
   {
      char hex[3];
      sprintf(hex, "%02x", digest[i]);
      strcat(md5hash, hex);
   }
   return md5hash;
}

char *BaseConvert::GetSHA1Hash(const char *data, int length)
{
   unsigned char digest[EVP_MAX_MD_SIZE];
   char *sha1hash = (char *)malloc(sizeof(char)*(EVP_MAX_MD_SIZE*2+1));
   sha1hash[0] = '\0';
   EVP_MD_CTX *ctx;
   ctx = EVP_MD_CTX_create();
   const EVP_MD *sha1 = EVP_sha1();
   EVP_DigestInit_ex(ctx, sha1, NULL);
   EVP_DigestUpdate(ctx, data, length);
   EVP_DigestFinal_ex(ctx, digest, NULL);
   EVP_MD_CTX_destroy(ctx);
   fSSLCheckError();
   for (int i = 0; i < sha1->md_size; i++)
   {
      char hex[3];
      sprintf(hex, "%02x", digest[i]);
      strcat(sha1hash, hex);
   }
   return sha1hash;
}

char *BaseConvert::XORBlockHashes(const char **hashes)
{
   const static char lookup_h2n[] = {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
                                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                                      -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
   const static char lookup_n2h[] = { '0', '1', '2', '3', '4', '5', '6', '7',
                                      '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
   int count = 0;
   if (hashes[0] == NULL) return NULL;
   int hashlen = strlen(hashes[0]);
   char *result = (char *)malloc(sizeof(char)*(hashlen+1));
   for (int i = 0; i < hashlen; i++) result[i] = '0';
   result[hashlen] = '\0';
   while (hashes[count] != NULL)
   {
      for (int i = 0; i < hashlen; i++)
      {
         int char1 = result[i];
         int char2 = (hashes[count])[i];
         int chr1_conv = (char1 < '0') ? -1 : ((char1 >= ('0' + 64)) ? -1 : lookup_h2n[char1 - '0']);
         int chr2_conv = (char2 < '0') ? -1 : ((char2 >= ('0' + 64)) ? -1 : lookup_h2n[char2 - '0']);
         if ((chr1_conv < 0) || (chr2_conv < 0))
         {
            errprint("invalid characters in hash value");
            return NULL;
         }
         result[i] = lookup_n2h[chr1_conv ^ chr2_conv];
      }
      count++;
   }
   return result;
}
